Skip to main content

Web Vitals & React Performance Debugging Guide

Core Web Vitals Overview

Largest Contentful Paint (LCP)

  • What: Measures loading performance - time until largest content element is visible
  • Target: Under 2.5s
  • Debugging:
import { getLCP } from 'web-vitals';

getLCP(console.log); // Basic logging
getLCP((metric) => {
// Send to analytics
analytics.send({
name: 'LCP',
value: metric.value,
id: metric.id,
element: metric.element // The actual LCP element
});
});

First Input Delay (FID)

  • What: Measures interactivity - time from user's first interaction to browser's response
  • Target: Under 100ms
  • Debugging:
import { getFID } from 'web-vitals';

getFID((metric) => {
console.log({
name: 'FID',
value: metric.value,
eventTarget: metric.eventTarget, // Element that was interacted with
eventType: metric.eventType // Type of interaction
});
});

Cumulative Layout Shift (CLS)

  • What: Measures visual stability - unexpected layout shifts
  • Target: Under 0.1
  • Debugging:
import { getCLS } from 'web-vitals';

getCLS((metric) => {
console.table(metric.entries.map(entry => ({
startTime: entry.startTime,
value: entry.value,
hadRecentInput: entry.hadRecentInput,
elements: entry.sources.map(source => source.node)
})));
});

React Performance Debugging Tools

React Developer Tools

// Enable React Developer Tools in development
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}

Custom Performance Hooks

import { useEffect, useRef } from 'react';

function useComponentTimer(componentName) {
const startTime = useRef();

useEffect(() => {
startTime.current = performance.now();

return () => {
const duration = performance.now() - startTime.current;
console.log(`${componentName} mounted for ${duration}ms`);
};
}, [componentName]);
}

Web Vitals in Next.js

// pages/_app.js
import { useEffect } from 'react';
import * as webVitals from 'web-vitals';

function MyApp({ Component, pageProps }) {
useEffect(() => {
webVitals.getCLS(console.log);
webVitals.getFID(console.log);
webVitals.getLCP(console.log);
webVitals.getFCP(console.log);
webVitals.getTTFB(console.log);
}, []);

return <Component {...pageProps} />;
}

Common Performance Issues & Solutions

Image Optimization

// Next.js Image component with optimization
import Image from 'next/image';

function OptimizedImage() {
return (
<Image
src="/large-image.jpg"
width={800}
height={600}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
priority={true} // For LCP images
/>
);
}

Code Splitting

// Using React.lazy for component-level code splitting
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}

Performance Monitoring

// Using Performance Observer API
const performanceObserver = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.entryType === 'largest-contentful-paint') {
console.log('LCP:', entry.startTime);
console.log('Element:', entry.element);
}
});
});

performanceObserver.observe({ entryTypes: ['largest-contentful-paint'] });

Debug Checklist

  1. Install Chrome DevTools extensions:

    • React Developer Tools
    • Performance insights
    • Lighthouse
  2. Enable Performance Tracing:

// Add to your app's entry point
if (process.env.NODE_ENV !== 'production') {
const { enableReactTracking } = require('@performance/react-tracker');
enableReactTracking({
logToConsole: true,
logToNetwork: true
});
}
  1. Monitor Layout Shifts:
let cumulativeScore = 0;

new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
cumulativeScore += entry.value;
console.log('Layout shift:', {
value: entry.value,
cumulative: cumulativeScore,
elements: entry.sources.map(source => source.node)
});
}
}
}).observe({ entryTypes: ['layout-shift'] });
  1. Implement Error Boundaries:
class PerformanceErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
console.error('Performance issue detected:', error);
console.log('Component stack:', errorInfo.componentStack);

// Log to your analytics service
logError({
type: 'PERFORMANCE',
error,
componentStack: errorInfo.componentStack
});
}

render() {
return this.props.children;
}
}